class Timespan < ActiveRecord::Base
  has_many(:search_runs)
  has_many(:searches, :through => :search_runs)
  
  def self.common
    find(:all, :conditions => "is_common=1", :order => "before_offset desc, before_date desc")
  end
  
  def self.find_chunks_between(start, stop)
    find(:all, :conditions => ["is_search_chunk=1 and ? <= after_date and after_date < ?", start, stop])
  end
  
  def self.count_chunks
    count(:conditions => ["is_search_chunk=1"])
  end
  
  def self.paginate_default_to_last(page)
    page = (count_chunks / CONF.saved_search_chunks_per_page).floor + 1 if page.nil?
    paginate(:page => page, :per_page => CONF.saved_search_chunks_per_page, :conditions => ["is_search_chunk=1"], :order => "after_date")
  end
  
  # ensures there are appropriate timespans, including the defaults
  # and timespans for all days up until now
  # also updates chunks
  def self.ensure_timespans
    # common 'in the last ...' spans
    find_or_create_by_after_offset_and_before_offset_and_is_common(-1, 0, true)
    find_or_create_by_after_offset_and_before_offset_and_is_common(-5, 0, true)
    find_or_create_by_after_offset_and_before_offset_and_is_common(-60, 0, true)
    find_or_create_by_after_offset_and_before_offset_and_is_common(-120, 0, true)
    find_or_create_by_after_offset_and_before_offset_and_is_common(-240, 0, true)
    find_or_create_by_after_offset_and_before_offset_and_is_common(-1440, 0, true)
    
    # get latest day
    last_day = find(:first, :conditions => "is_day = 1", :order => "after_date desc")
    last_day_end = last_day ? last_day.before_date : (Time.zone.now - 5.days).midnight
    
    # create days up to now
    midnight_tmrw = Time.zone.now.midnight + 1.day
    while last_day_end < midnight_tmrw do 
      create(:after_date => last_day_end, :before_date => last_day_end + 1.day, :is_day => true, :is_common => true)
      last_day_end += 1.day
    end
    
    ensure_chunks
  end

  # ensures there are n-minute search chunks from chunk_start_date up till now
  # assumes that everything is ok before the latest chunk
  def self.ensure_chunks
    # get the latest chunk
    last_chunk = find(:first, :conditions => "is_search_chunk = 1", :order => "after_date desc")
    last_chunk_date = last_chunk ? last_chunk.before_date : (Time.zone.now - 5.days).midnight
    
    while last_chunk_date < Time.zone.now
      create(:after_date => last_chunk_date, :before_date => last_chunk_date + CONF.chunk_size, :is_search_chunk => true)
      last_chunk_date += CONF.chunk_size
    end
  end
  
  # constructs before: and after: search terms for this timespan
  # only works for absolute timespans for now
  def search_string
    "after:\"#{after_date.basic_format}\" before:\"#{before_date.basic_format}\""
  end
  
  def name
    return @name if @name
    
    # if this is an offset timezone, just figure out what the largest unit for before and after are, and return them
    if is_relative?
      bounds = [after_offset, before_offset].collect{|t| relative_bound(t)}
      if bounds[1] == "now"
        bounds.delete_at(1)
        bounds[0] = "the last " + bounds[0].gsub(/( ago|1 )/, "")
      end
    else
      # else its absolute
      # if hour, minute, and second are all zero, don't include them in time format
      bounds = [after_date, before_date].collect{|t| t.strftime("%a %b %e" + (t == t.midnight ? "" : " %T"))}
      
      # if from midnight to midnight, don't need the stop date, nor the time
      if after_date.midnight == after_date && (before_date - after_date) % 1.day == 0
        bounds.delete_at(1)
        bounds[0] = bounds[0].gsub(" %T", "")
      end
    end
    @name = bounds.join(" to ")
    return @name
  end
  
  def sql_fragment(col = "reports.authored_at")
    if is_relative?
      after = Time.now + after_offset * 60
      before = Time.now + before_offset * 60
    else
      after = after_date
      before = before_date
    end
    after = after.utc.strftime("%Y-%m-%d %T")
    before = before.utc.strftime("%Y-%m-%d %T")
    "(#{col} >= '#{after}' and #{col} < '#{before}')"
  end
      
  
  def relative_bound(t)
    return "now" if t == 0
    modifier = t > 0 ? "hence" : "ago"
    abs = t.abs * 60
    unit = ""
    num = ""
    ["day", "hour", "minute", "second"].each{|u| unit = u and break if (num = (abs / 1.send(u)).floor) > 0}
    pl = num == 1 ? "" : "s"
    "#{num} #{unit}#{pl} #{modifier}"
  end
  
  def is_relative?
    !after_offset.nil?
  end
    
end
